"""数据安全模块 - 提供金融领域专用的数据脱敏、加密、权限校验等企业级安全功能

该模块专为金融情感分析平台设计，确保敏感金融数据在处理、存储和传输过程中的安全性，
符合金融行业合规要求并提供完善的数据访问控制机制。
"""

import os
import re
import json
import hashlib
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List, Union, Tuple

import bcrypt
import cryptography
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

from .config_loader import ConfigLoader, get_config
from .logger import get_logger, log_audit
from .exceptions import SecurityException, PermissionDeniedException

# 初始化日志
logger = get_logger(__name__)


class DataSecurity:
    """金融领域数据安全管理类

    提供全面的数据安全功能，包括：
    - 敏感数据脱敏与还原
    - 数据加密与解密
    - 用户权限校验
    - 数据访问控制
    - 安全审计日志
    - 合规性检查
    """

    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """初始化数据安全管理器

        Args:
            config: 安全配置参数，如果为None则使用默认配置
        """
        # 加载配置
        self.config_loader = ConfigLoader()
        self.config = config or self.config_loader.get('security', {})

        # 初始化加密密钥
        self._init_encryption_keys()

        # 敏感数据模式定义
        self._init_sensitive_patterns()

        # 权限控制配置
        self.access_control = self.config.get('access_control', {})

        logger.info("金融数据安全管理器初始化成功")

    def _init_encryption_keys(self) -> None:
        """初始化加密密钥系统"""
        # 主密钥管理
        key_file_path = self.config.get('key_file_path', 'keys/master.key')

        # 确保密钥存储目录存在
        os.makedirs(os.path.dirname(key_file_path), exist_ok=True)

        # 如果密钥文件不存在，则生成新密钥
        if not os.path.exists(key_file_path):
            try:
                # 生成主密钥
                master_key = Fernet.generate_key()
                with open(key_file_path, 'wb') as f:
                    f.write(master_key)

                # 设置文件权限（仅当前用户可访问）
                os.chmod(key_file_path, 0o600)
                logger.info(f"生成新的加密主密钥: {key_file_path}")
                log_audit('system', 'generate_key', key_file_path, 'success')
            except Exception as e:
                logger.error(f"初始化加密密钥失败: {str(e)}")
                log_audit('system', 'generate_key', key_file_path, 'failed', {'error': str(e)})
                raise SecurityException(f"加密密钥初始化失败: {str(e)}")

        # 加载主密钥
        try:
            with open(key_file_path, 'rb') as f:
                master_key = f.read()

            # 创建Fernet加密器实例
            self.fernet = Fernet(master_key)
        except Exception as e:
            logger.error(f"加载加密密钥失败: {str(e)}")
            raise SecurityException(f"加密密钥加载失败: {str(e)}")

    def _init_sensitive_patterns(self) -> None:
        """初始化金融领域敏感数据模式匹配规则"""
        # 基于金融行业标准定义敏感数据模式
        self.sensitive_patterns = {
            # 个人身份信息
            'id_card': {
                'pattern': r'(\d{17}[\dXx])',
                'mask': lambda x: x[:6] + '********' + x[14:]
            },
            'phone': {
                'pattern': r'(1[3-9]\d{9})',
                'mask': lambda x: x[:3] + '****' + x[7:]
            },
            'name': {
                'pattern': r'([\u4e00-\u9fa5]{2,4})',
                'mask': lambda x: x[0] + '*' * (len(x) - 1) if len(x) > 1 else x
            },

            # 金融账户信息
            'bank_card': {
                'pattern': r'(\d{16,19})',
                'mask': lambda x: x[:4] + '********' + x[-4:]
            },
            'credit_card': {
                'pattern': r'(\d{13,19})',
                'mask': lambda x: x[:4] + '********' + x[-4:]
            },
            'account_number': {
                'pattern': r'(account\s*number[:：]\s*|账号[:：]\s*)(\w{8,20})',
                'mask': lambda x: x[0] + '******' + x[-2:] if len(x) > 7 else x
            },

            # 地址信息
            'address': {
                'pattern': r'(省|市|区|县|街道|路|巷|弄)\s*[\u4e00-\u9fa5\d]+',
                'mask': lambda x: re.sub(r'[\u4e00-\u9fa5\d]+$', '***', x)
            },

            # 金融敏感信息
            'stock_position': {
                'pattern': r'(持仓|持有)\s*(\d+)\s*(股|手)',
                'mask': lambda x: x[0] + '***' + x[-1] if len(x) > 1 else x
            },
            'transaction_amount': {
                'pattern': r'(成交|买入|卖出)\s*金额[:：]?\s*([\d,]+\.?\d*)',
                'mask': lambda x: x[0] + '***' + x[-1] if len(x) > 1 else x
            },
            'financial_account': {
                'pattern': r'(资金账户|交易账户|资产账户)[:：]?\s*([A-Za-z0-9]{8,20})',
                'mask': lambda x: x[0] + '********' + x[-1] if len(x) > 1 else x
            }
        }

    def mask_sensitive_data(self, data: Any,
                          include_types: Optional[List[str]] = None,
                          exclude_types: Optional[List[str]] = None) -> Any:
        """对数据中的敏感信息进行脱敏处理

        Args:
            data: 待脱敏的数据（支持字符串、字典、列表等）
            include_types: 要包含的敏感数据类型列表，None表示全部包含
            exclude_types: 要排除的敏感数据类型列表，None表示不排除

        Returns:
            脱敏后的数据

        Raises:
            SecurityException: 数据脱敏过程中发生错误
        """
        try:
            # 确定要处理的敏感数据类型
            types_to_process = set(self.sensitive_patterns.keys())

            if include_types:
                types_to_process.intersection_update(include_types)
            if exclude_types:
                types_to_process.difference_update(exclude_types)

            # 根据数据类型进行处理
            if isinstance(data, str):
                return self._mask_string(data, types_to_process)
            elif isinstance(data, dict):
                return {k: self.mask_sensitive_data(v, include_types, exclude_types)
                        for k, v in data.items()}
            elif isinstance(data, list):
                return [self.mask_sensitive_data(item, include_types, exclude_types)
                        for item in data]
            else:
                return data
        except Exception as e:
            logger.error(f"数据脱敏失败: {str(e)}")
            raise SecurityException(f"数据脱敏错误: {str(e)}")

    def _mask_string(self, text: str, types_to_process: set) -> str:
        """对字符串中的敏感信息进行脱敏"""
        masked_text = text

        for data_type, pattern_info in self.sensitive_patterns.items():
            if data_type in types_to_process:
                pattern = pattern_info['pattern']
                mask_func = pattern_info['mask']

                # 查找并替换所有匹配的敏感数据
                def replace_match(match):
                    # 处理分组捕获的情况
                    if len(match.groups()) > 1:
                        # 对于带前缀的模式，只脱敏值部分
                        prefix = match.group(1)
                        sensitive_value = match.group(2)
                        return prefix + mask_func(sensitive_value)
                    else:
                        return mask_func(match.group(0))

                masked_text = re.sub(pattern, replace_match, masked_text)

        return masked_text

    def encrypt_data(self, data: Any) -> str:
        """加密数据

        Args:
            data: 待加密的数据

        Returns:
            加密后的字符串

        Raises:
            SecurityException: 加密过程中发生错误
        """
        try:
            # 将数据转换为JSON字符串
            data_str = json.dumps(data, ensure_ascii=False)
            # 编码为字节并加密
            encrypted_bytes = self.fernet.encrypt(data_str.encode('utf-8'))
            # 转换为Base64字符串返回
            return encrypted_bytes.decode('utf-8')
        except Exception as e:
            logger.error(f"数据加密失败: {str(e)}")
            raise SecurityException(f"数据加密错误: {str(e)}")

    def decrypt_data(self, encrypted_data: str) -> Any:
        """解密数据

        Args:
            encrypted_data: 加密的数据字符串

        Returns:
            解密后的原始数据

        Raises:
            SecurityException: 解密过程中发生错误
        """
        try:
            # 解码Base64字符串为字节
            encrypted_bytes = encrypted_data.encode('utf-8')
            # 解密数据
            decrypted_bytes = self.fernet.decrypt(encrypted_bytes)
            # 解码为字符串并解析JSON
            data_str = decrypted_bytes.decode('utf-8')
            return json.loads(data_str)
        except Exception as e:
            logger.error(f"数据解密失败: {str(e)}")
            raise SecurityException(f"数据解密错误: {str(e)}")

    def hash_data(self, data: Any, salt: Optional[str] = None) -> str:
        """对数据进行哈希处理

        Args:
            data: 待哈希的数据
            salt: 可选的盐值，如果为None则使用系统默认盐值

        Returns:
            哈希后的字符串
        """
        # 将数据转换为字符串
        data_str = json.dumps(data, ensure_ascii=False)

        # 使用盐值增强安全性
        if salt is None:
            salt = self.config.get('default_salt', 'fin_senti_platform_salt_2023')

        # 创建哈希对象
        hasher = hashlib.sha256()
        # 更新哈希对象
        hasher.update((data_str + salt).encode('utf-8'))
        # 返回十六进制哈希值
        return hasher.hexdigest()

    def verify_permission(self, user_id: str, resource: str, action: str) -> bool:
        """验证用户是否具有对资源执行特定操作的权限

        Args:
            user_id: 用户ID
            resource: 资源名称
            action: 操作类型（如read, write, execute）

        Returns:
            有权限返回True，否则返回False

        Raises:
            PermissionDeniedException: 权限验证失败
        """
        try:
            # 检查用户角色权限
            user_roles = self._get_user_roles(user_id)

            # 检查每个角色是否有相应权限
            for role in user_roles:
                if self._check_role_permission(role, resource, action):
                    logger.info(f"权限验证通过: 用户={user_id}, 资源={resource}, 操作={action}")
                    log_audit(user_id, action, resource, 'success')
                    return True

            # 权限验证失败
            logger.warning(f"权限验证失败: 用户={user_id}, 资源={resource}, 操作={action}")
            log_audit(user_id, action, resource, 'failed', {'reason': 'insufficient_permissions'})
            raise PermissionDeniedException(f"用户 {user_id} 没有权限执行 {action} 操作于 {resource} 资源")
        except PermissionDeniedException:
            raise
        except Exception as e:
            logger.error(f"权限验证过程异常: {str(e)}")
            raise SecurityException(f"权限验证错误: {str(e)}")

    def _get_user_roles(self, user_id: str) -> List[str]:
        """获取用户的角色列表"""
        # 从配置或用户管理系统获取用户角色
        user_roles_config = self.access_control.get('user_roles', {})
        return user_roles_config.get(user_id, ['default'])

    def _check_role_permission(self, role: str, resource: str, action: str) -> bool:
        """检查角色是否具有对资源执行特定操作的权限"""
        # 从配置中获取角色权限
        role_permissions = self.access_control.get('role_permissions', {})
        role_rules = role_permissions.get(role, {})

        # 检查资源权限规则
        resource_rules = role_rules.get(resource, [])

        # 如果操作在资源权限规则中，或者规则包含'*'（表示所有操作），则有权限
        return action in resource_rules or '*' in resource_rules

    def check_text_security(self, text: str) -> bool:
        """检查文本内容安全性

        Args:
            text: 待检查的文本

        Returns:
            文本安全返回True，否则返回False
        """
        # 检查是否包含恶意内容
        malicious_patterns = [
            # 注入攻击模式
            r'\bsql\b.*\bselect\b.*\bfrom\b',
            r'\bunion\b.*\bselect\b',
            r'<\s*script\b',
            # 敏感词汇
            r'\b洗钱\b', r'\b欺诈\b', r'\b非法集资\b',
            # 其他安全风险模式
            r'\beval\b\s*\(',
            r'\bexec\b\s*\(',
        ]

        for pattern in malicious_patterns:
            if re.search(pattern, text, re.IGNORECASE):
                logger.warning(f"检测到不安全内容: {text[:100]}...")
                return False

        return True

    def generate_api_key(self, user_id: str, expire_days: int = 30) -> Tuple[str, datetime]:
        """生成API密钥

        Args:
            user_id: 用户ID
            expire_days: 密钥有效期（天）

        Returns:
            (api_key, expiration_date) 元组
        """
        # 生成过期时间
        expiration_date = datetime.now() + timedelta(days=expire_days)

        # 创建API密钥内容
        api_key_content = {
            'user_id': user_id,
            'expire_at': expiration_date.isoformat(),
            'created_at': datetime.now().isoformat(),
            'token': os.urandom(32).hex()
        }

        # 加密API密钥内容
        api_key = self.encrypt_data(api_key_content)

        logger.info(f"为用户生成新的API密钥: {user_id}, 过期时间: {expiration_date}")
        log_audit('system', 'generate_api_key', user_id, 'success', {'expire_days': expire_days})
        return api_key, expiration_date

    def validate_api_key(self, api_key: str) -> Dict[str, Any]:
        """验证API密钥

        Args:
            api_key: 待验证的API密钥

        Returns:
            API密钥信息（包含user_id等）

        Raises:
            SecurityException: API密钥无效或已过期
        """
        try:
            # 解密API密钥
            api_key_info = self.decrypt_data(api_key)

            # 检查是否过期
            expire_at = datetime.fromisoformat(api_key_info['expire_at'])
            if datetime.now() > expire_at:
                logger.warning(f"API密钥已过期: user_id={api_key_info.get('user_id')}")
                log_audit('system', 'validate_api_key', 'api_key', 'failed', {'reason': 'expired'})
                raise SecurityException("API密钥已过期")

            logger.info(f"API密钥验证成功: user_id={api_key_info.get('user_id')}")
            log_audit(api_key_info.get('user_id'), 'validate_api_key', 'api_key', 'success')
            return api_key_info
        except SecurityException:
            raise
        except Exception as e:
            logger.error(f"API密钥验证失败: {str(e)}")
            log_audit('unknown', 'validate_api_key', 'api_key', 'failed', {'reason': 'invalid'})
            raise SecurityException(f"无效的API密钥: {str(e)}")

    def sanitize_input(self, input_data: Any, allowed_types: Optional[List[type]] = None) -> Any:
        """净化输入数据，防止注入攻击

        Args:
            input_data: 待净化的输入数据
            allowed_types: 允许的数据类型列表，None表示默认类型

        Returns:
            净化后的数据
        """
        # 默认允许的类型
        if allowed_types is None:
            allowed_types = [str, int, float, bool, list, dict, type(None)]

        # 检查数据类型
        if not isinstance(input_data, tuple(allowed_types)):
            logger.warning(f"不允许的输入类型: {type(input_data)}")
            raise SecurityException(f"无效的输入类型: {type(input_data).__name__}")

        # 递归处理复杂数据类型
        if isinstance(input_data, dict):
            return {k: self.sanitize_input(v, allowed_types) for k, v in input_data.items()}
        elif isinstance(input_data, list):
            return [self.sanitize_input(item, allowed_types) for item in input_data]
        elif isinstance(input_data, str):
            # 对字符串进行特殊字符过滤
            # 移除潜在的脚本标签
            sanitized = re.sub(r'<script.*?>.*?</script>', '', input_data, flags=re.DOTALL)
            # 移除SQL注入关键字
            sql_keywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'CREATE']
            for keyword in sql_keywords:
                sanitized = re.sub(rf'\b{keyword}\b', '', sanitized, flags=re.IGNORECASE)
            return sanitized

        return input_data

    def get_security_compliance_status(self) -> Dict[str, Any]:
        """获取安全合规状态报告

        Returns:
            包含各项安全合规检查结果的字典
        """
        compliance_status = {
            'encryption_enabled': hasattr(self, 'fernet') and self.fernet is not None,
            'key_rotation_status': self._check_key_rotation_status(),
            'access_control_enabled': len(self.access_control) > 0,
            'logging_enabled': True,
            'compliance_score': self._calculate_compliance_score(),
            'last_security_audit': self.config.get('last_security_audit', 'Never'),
        }

        logger.info("生成安全合规状态报告")
        return compliance_status

    def _check_key_rotation_status(self) -> str:
        """检查密钥轮换状态"""
        # 简化实现，实际项目中可能需要更复杂的密钥轮换策略
        key_age_days = self.config.get('key_age_days', 90)
        if key_age_days <= 90:
            return 'Compliant'
        elif key_age_days <= 180:
            return 'Warning'
        else:
            return 'Non-Compliant'

    def _calculate_compliance_score(self) -> int:
        """计算安全合规分数"""
        # 简化实现，实际项目中可能需要更复杂的评分算法
        score = 0
        if hasattr(self, 'fernet') and self.fernet is not None:
            score += 30
        if len(self.access_control) > 0:
            score += 25
        if self._check_key_rotation_status() == 'Compliant':
            score += 20
        # 其他合规检查项
        score += 25  # 基础合规分数

        return min(100, score)


# 单例模式实现，确保全局只有一个DataSecurity实例
_security_instance = None


def get_security_manager(config: Optional[Dict[str, Any]] = None) -> DataSecurity:
    """获取数据安全管理器的单例实例

    Args:
        config: 可选的配置参数

    Returns:
        DataSecurity实例
    """
    global _security_instance
    if _security_instance is None:
        _security_instance = DataSecurity(config)
    return _security_instance


# 安全上下文管理器，用于简化安全操作
class secure_context:
    """安全上下文管理器，用于简化敏感数据处理操作"""

    def __init__(self, user_id: str, resource: str, action: str):
        """初始化安全上下文

        Args:
            user_id: 用户ID
            resource: 资源名称
            action: 操作类型
        """
        self.user_id = user_id
        self.resource = resource
        self.action = action
        self.security_manager = get_security_manager()
        self.access_granted = False

    def __enter__(self):
        """进入上下文，验证权限"""
        try:
            self.access_granted = self.security_manager.verify_permission(
                self.user_id, self.resource, self.action
            )
            return self
        except PermissionDeniedException:
            self.access_granted = False
            raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文，记录访问日志"""
        success = exc_type is None and self.access_granted
        details = {'exception': str(exc_val) if exc_val else None}

        # 已经在verify_permission中记录了审计日志，这里不再重复记录

        # 不抑制异常
        return False

    def process_data(self, data: Any, operation: str = 'mask') -> Any:
        """在安全上下文中处理数据

        Args:
            data: 待处理的数据
            operation: 处理操作类型（mask, encrypt, sanitize等）

        Returns:
            处理后的数据
        """
        if not self.access_granted:
            raise PermissionDeniedException("在安全上下文中未获得访问权限")

        if operation == 'mask':
            return self.security_manager.mask_sensitive_data(data)
        elif operation == 'encrypt':
            return self.security_manager.encrypt_data(data)
        elif operation == 'sanitize':
            return self.security_manager.sanitize_input(data)
        else:
            raise ValueError(f"不支持的操作: {operation}")